{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2010年到2020年《心理学报》论文信息\n",
    "《心理学报》由中国心理学会主办，是国内最重要、最具有影响力的心理学期刊。[官方网站](http://journal.psych.ac.cn/xlxb/CN/0439-755X/home.shtml) 。使用网络爬虫爬取《心理学报》从2010年第一期到2020年第十一期（2020年第十二期尚未出版）刊发的文章的基本信息：中英文标题、作者、关键词、类型、doi等，保存在csv文件中。\n",
    "## 需要的模块\n",
    "1. requests\n",
    "\n",
    "使用其中的 requests.get()函数来获取网页的HTML数据\n",
    "2. BeautifulSoup\n",
    "\n",
    "用来解析抓取的HTML数据，解析之后可以用find()和find_all()来提取出其中的HTML标签\n",
    "\n",
    "3. tqdm\n",
    "\n",
    "tqdm()可以为for循环生成一个进度条，显示循环的次数。在出现error时也可以看到是哪一次的执行出现了问题\n",
    "\n",
    "4. csv\n",
    "\n",
    "用来将爬取的信息写入csv文件\n",
    "5. time\n",
    "`time.sleep()`函数可以将程序暂时挂起一段时间，避免访问过于频繁导致服务器拒绝访问\n",
    "## 基本过程\n",
    "### 定义函数get_bf()\n",
    "为了提高代码复用性，定义函数`get_bf()`，其作用是抓取网页的HTML数据，提取其中的文本并用BeautifulSoup解析。\n",
    "### 爬取各期的URL\n",
    "心理学报的[过刊浏览](http://journal.psych.ac.cn/xlxb/CN/article/showOldVolumn.do)页面包含了已经出版的各期的URL，需要先爬取从2010年第1期到最新一期的URL，再从每一期的URL中爬取刊发的论文的URL。\n",
    "\n",
    "过刊浏览页面将已经出版的期刊的链接都放在了网页中间的表格里，使用审查元素发现表格内的信息都储存在“tbody”（也就是表格）这个标签内，每一期都是一个“a”标签，从新到旧一次排列，比如2020年第11期是\n",
    "```HTML\n",
    "<td><a href=\"../volumn/volumn_341.shtml\">\n",
    "\t\t\t\t\t\t\t\t\tNo.11</a>\n",
    "</td>\n",
    "```\n",
    "而这一期的网址是http://journal.psych.ac.cn/xlxb/CN/volumn/volumn_341.shtml 和td标签内的` href=\"../volumn/volumn_341.shtml\"`对比一下，发现将“href”中的内容去掉开头的两个“.”再拼接到“http://journal.psych.ac.cn/xlxb/CN” 之后就可以得到各期的网址了。\n",
    "\n",
    "使用`find_all()`提取出\"tbody\"内所有的“a”标签，然后用`get()`获取href，和“http://journal.psych.ac.cn/xlxb/CN” 缀连在一起，保存到一个列表中，就得到了所有过刊的URL库。然后通过切片的方法得到从2010年第1期到最新一期的网址。《心理学报》是月刊，所以共有12\\*10+11=131期。切片得到url列表的前131项就是需要的所有URL。\n",
    "### 从每一期的网址中抓取每一篇文章的URL\n",
    "以[2020年第11期](http://journal.psych.ac.cn/xlxb/CN/volumn/volumn_341.shtml)为例，审查元素之后可以发现所有的论文的信息都保存在“tbody”标签内（也就是网页中间的表格），而每一篇论文都是一个独立的“div”标签，论文的题目和网址被保存在其中的一个“a”标签内，可以根据`class = txt_biaoti`来将这些“a”标签提取出来\n",
    "```HTML\n",
    "<a target=\"_blank\" href=\"http://journal.psych.ac.cn/xlxb/CN/10.3724/SP.J.1041.2020.01288\" class=\"txt_biaoti\">睾酮素与反社会倾向未成年犯的攻击行为：敌意注意偏向的中介和皮质醇的调节作用</a>\n",
    "```\n",
    "因为`class`是python关键词，所以筛选语句写成\n",
    "`find_all('a',class = 'txt_biaoti')`就会出错，\n",
    "\n",
    "可行的写法是\n",
    "`find_all('a',attrs = {'class':'txt_biaoti'})`，通过`attrs`来指定标签的\"class\"属性为“txt_biaoti”\n",
    "\n",
    "使用for循环依次遍历所有的过刊URL，将抓取的论文URL保存在一个列表中，就得到了所有论文的URL库\n",
    "### 获取每一篇论文的信息\n",
    "以[这一篇](http://journal.psych.ac.cn/xlxb/CN/10.3724/SP.J.1041.2020.01288)为例，审查元素之后可以发现论文的大多数信息都能在HTML的“head”标签内找到。如中英文标题、作者、关键词、出版时间等\n",
    "```HTML\n",
    "<meta name=\"citation_title\" xml:lang=\"zh\" content=\"睾酮素与反社会倾向未成年犯的攻击行为：敌意注意偏向的中介和皮质醇的调节作用\">\n",
    "<meta name=\"citation_title\" xml:lang=\"en\" content=\"Testosterone and aggressive behavior in juvenile offenders with antisocial tendency: The mediation effect of hostile attention bias and the moderation effect of cortisol\">\n",
    "<meta name=\"citation_authors\" xml:lang=\"zh\" content=\"任志洪, 赵子仪, 余香莲, 赵春晓, 张琳, 林羽中, 张微\">\n",
    "\n",
    "```\n",
    "所以只要根据根据“name”这一属性将需要的“meta”标签提取出来即可\n",
    "### 排除已经失效的页面\n",
    "在程序运行中发现有些论文的页面已经失效，变成了404，[比如这一篇](http://journal.psych.ac.cn/xlxb/CN/No.12,%201507-1518)。所以增加了`if`语句来判断网页是否有效。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#装载需要的模块\n",
    "import requests\n",
    "from bs4 import BeautifulSoup\n",
    "from tqdm import tqdm\n",
    "import csv\n",
    "import time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#定义函数get_bf，抓取网页\n",
    "def get_bf(target):\n",
    "    req = requests.get(url = target) #抓取HTML数据\n",
    "    req.encoding = 'utf-8'#网页的编码为UTF-8\n",
    "    html = req.text #提取出文本\n",
    "    bf = BeautifulSoup(html,'lxml') #解析，解析器为“lxml”\n",
    "    return bf"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#从“过刊浏览”页面抓取各期的URL\n",
    "#首先获取各期的URL\n",
    "server = \"http://journal.psych.ac.cn/xlxb/CN\"#心理学报各期的URL开头\n",
    "target = 'http://journal.psych.ac.cn/xlxb/CN/article/showOldVolumn.do'#心理学报“过刊浏览”页面\n",
    "vol_url = list() #用来储存各期的URL\n",
    "index_bs = get_bf(target).find('tbody').find_all('a')#抓取页面，并提取出含有每一期URL的标签\n",
    "#生成过刊URL库\n",
    "for vol in tqdm(index_bs[0:131]):#用tqdm()生成一个进度条\n",
    "    vol_url.append(server + vol.get('href').strip('.'*2))#将href去掉开头的两个点之后连在服务器地址后面\n",
    "print(vol_url)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#生成论文URL库\n",
    "article_url = list()#用来储存所有文章的URL\n",
    "for vol in tqdm(vol_url):#每一期过刊\n",
    "    target = vol\n",
    "    articles = get_bf(target).find_all('a',attrs = {'class':'txt_biaoti'})#抓取每一期内的所有论文\n",
    "    for article in articles:#每一期过刊内的每一篇论文\n",
    "        article_url.append(article.get('href'))#将每一篇论文的URL依次提取出来并存入\n",
    "print(article_url)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "#建立csv文件存储结果\n",
    "with open('xlxb.csv','a',newline = '',encoding = 'utf-8-sig') as f:#创建csv文件，指定写入方式为继续添加，编码为UTF-8，行与行之间没有分隔符\n",
    "    csv_w = csv.writer(f)\n",
    "    #第一行是变量名\n",
    "    csv_w.writerow([u'中文标题',u'英文标题',u'类型',u'作者',u'出版时间',u'doi',u'链接',u'关键词'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 1480/1480 [36:23<00:00,  1.48s/it] \n"
     ]
    }
   ],
   "source": [
    "#用条件判断跳过404的网页\n",
    "for article in tqdm(article_url):\n",
    "    target = article#依次将每一篇论文的URL作为抓取对象\n",
    "    article_bs = get_bf(target)\n",
    "    title = article_bs.find('title').string#抓取网页标题，用来判断网页是否失效\n",
    "    if title != 'HTTP404 无法找到页面':#已失效的网页标题为“HTTP404 无法找到页面”，网页有效则进行爬取，失效则跳过\n",
    "        #中英文的标签均为 “name = citation_title”,第一个是中文，第二个是英文\n",
    "        title_cn = article_bs.find_all('meta',attrs = {'name':'citation_title'})[0].get('content').strip()\n",
    "        title_en = article_bs.find_all('meta',attrs = {'name':'citation_title'})[1].get('content').strip()\n",
    "        #论文类型存储在网页内而不是head标签中\n",
    "        genre = article_bs.find('p',attrs = {'class':'clearfix'}).find('span',attrs = {'class':'pull-left'}).string.strip('•')\n",
    "        #作者，出版日期，doi号都可以从head中找到\n",
    "        authors = article_bs.find_all('meta',attrs = {'name':'citation_authors'})[0].get('content').strip()\n",
    "        published_date = article_bs.find('meta',attrs = {'name':'citation_publication_date'}).get('content').strip()\n",
    "        doi = article_bs.find('meta',attrs = {'name':'citation_doi'}).get('content').strip()\n",
    "        #将关键词分别保存\n",
    "        key_words = article_bs.find_all('meta',attrs = {'name':'citation_keywords','xml:lang':'zh'})\n",
    "        kw = list()\n",
    "        for kws in key_words:\n",
    "            kw.append(kws.get('content'))\n",
    "        #论文链接就是URL\n",
    "        link = article.strip()\n",
    "        with open('xlxb.csv','a',newline = '',encoding = 'utf-8') as f:#将获取的信息写入csv文件中\n",
    "            csv_w = csv.writer(f)\n",
    "            csv_w.writerow([title_cn,title_en,genre,authors,published_date,doi,link]+kw)\n",
    "        time.sleep(1)#延时1秒后执行下一次"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
